/* *****************************************************************************
 * JFab (http://code.google.com/p/jfab)
 * Copyright (c) 2011 JFab.org
 * Admin jfab@jeffreythompson.net
 *
 * See the file "LICENSE.txt" for information on usage and redistribution of
 * this file, and for a DISCLAIMER OF ALL WARRANTIES.
 * *****************************************************************************/

package org.jfab.core.inflator;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import org.jfab.core.collection.CGAnnotationList;
import org.jfab.core.collection.CGConstructorList;
import org.jfab.core.collection.CGFieldList;
import org.jfab.core.collection.CGInitializationBlockList;
import org.jfab.core.collection.CGInterfaceList;
import org.jfab.core.collection.CGMethodList;
import org.jfab.core.collection.CGPropertyList;
import org.jfab.core.collection.CGTypeList;
import org.jfab.core.collection.CommentList;
import org.jfab.core.collection.MethodBodyList;
import org.jfab.core.collection.ModifierList;
import org.jfab.core.factory.CGAnnotationFactory;
import org.jfab.core.factory.CGClassFactory;
import org.jfab.core.factory.CGConstructorFactory;
import org.jfab.core.factory.CGFieldFactory;
import org.jfab.core.factory.CGMethodFactory;
import org.jfab.core.factory.CGParameterFactory;
import org.jfab.core.factory.CGTypeFactory;
import org.jfab.core.factory.StringFactory;
import org.jfab.core.model.CGClass;
import org.jfab.core.model.CGConstructor;
import org.jfab.core.model.CGField;
import org.jfab.core.model.CGInterface;
import org.jfab.core.model.CGMethod;
import org.jfab.core.model.CGType;
import org.jfab.core.model.CGTypeUtilities;
import org.jfab.core.model.Modifier;
import org.jfab.core.util.TypeParameterMap;

/**
 * Provides a default implementation of a code generator class inflator.
 */
public final class DefaultCGClassInflator extends CGConcreteUnitInflator<CGClass> implements CGClassInflator
{
    /** Annotation factory. */
    private final CGAnnotationFactory _annotationFactory;

    /** Class factory. */
    private final CGClassFactory _classFactory;

    /** Cloneable code generator type. */
    private final CGType _cloneableType;

    /**
     * Construct this object with the given parameters.
     * 
     * @param stringFactory String factory. (required)
     * @param cgTypeUtils Code generator type utilities. (required)
     * @param cgTypeFactory Code generator type factory. (required)
     * @param fieldFactory Field factory. (required)
     * @param fieldComparator Field comparator. (required)
     * 
     * @param parameterFactory Parameter factory. (required)
     * @param constructorFactory Constructor factory. (required)
     * @param constructorComparator Constructor comparator. (required)
     * @param methodFactory Method factory. (required)
     * @param methodComparator Method comparator. (required)
     * 
     * @param annotationFactory Annotation factory. (required)
     * @param classFactory Class factory. (required)
     */
    public DefaultCGClassInflator(final StringFactory stringFactory, final CGTypeUtilities cgTypeUtils,
            final CGTypeFactory cgTypeFactory, final CGFieldFactory fieldFactory,
            final Comparator<CGField> fieldComparator, final CGParameterFactory parameterFactory,
            final CGConstructorFactory constructorFactory, final Comparator<CGConstructor> constructorComparator,
            final CGMethodFactory methodFactory, final Comparator<CGMethod> methodComparator,
            final CGAnnotationFactory annotationFactory, final CGClassFactory classFactory)
    {
        super(stringFactory, cgTypeUtils, cgTypeFactory, fieldFactory, fieldComparator, parameterFactory,
                constructorFactory, constructorComparator, methodFactory, methodComparator);

        Validate.notNull(annotationFactory, "annotationFactory is null");
        Validate.notNull(classFactory, "classFactory is null");

        _annotationFactory = annotationFactory;
        _classFactory = classFactory;

        _cloneableType = getCgTypeFactory().createType(Cloneable.class);
    }

    @Override
    public final CGClass inflate(final CGClass cgClass, final CGInterfaceList superInterfaces)
    {
        final CGTypeList referencedTypes = new CGTypeList(cgClass.getReferencedTypes());
        final String packageName = cgClass.getPackageName();
        final CommentList commentLines = new CommentList(cgClass.getCommentLines());
        final CGAnnotationList annotations = new CGAnnotationList(cgClass.getAnnotations());
        final ModifierList modifiers = new ModifierList(cgClass.getModifiers());
        final String className = cgClass.getName();
        final List<String> typeParameters = cgClass.getTypeParameters();
        final CGType extendsType = cgClass.getExtendsType();
        final CGTypeList implementsInterfaces = new CGTypeList(cgClass.getImplementsInterfaces());
        final TypeParameterMap typeParameterMap = createTypeParameterMap(cgClass, superInterfaces);
        final CGInitializationBlockList initializationBlocks = new CGInitializationBlockList(
                cgClass.getInitializationBlocks());
        final CGFieldList fields = createFields(cgClass, superInterfaces, typeParameterMap);
        final CGConstructorList constructors = createConstructors(cgClass, superInterfaces, fields);
        final CGMethodList methods = createMethods(cgClass, superInterfaces, fields, typeParameterMap);

        return getClassFactory().createClass(referencedTypes, packageName, commentLines, annotations, modifiers,
                className, typeParameters, extendsType, implementsInterfaces, initializationBlocks, constructors,
                fields, methods);
    }

    /**
     * @param cgClass Code generator class.
     * @param superInterfaces Super interfaces.
     * @param inflatedFields Inflated fields.
     * 
     * @return new constructors created from the given parameter.
     */
    protected final CGConstructorList createConstructors(final CGClass cgClass, final CGInterfaceList superInterfaces,
            final CGFieldList inflatedFields)
    {
        final ModifierList modifiers = new ModifierList(Modifier.PUBLIC);

        final CGConstructorList answer = super.createConstructors(modifiers, cgClass, inflatedFields);

        if (cgClass.isNoArgConstructor())
        {
            final String className = cgClass.getName();
            final CGConstructor constructor = createNoArgConstructor(className);
            answer.add(constructor);

            Collections.sort(answer, getConstructorComparator());
        }

        return answer;
    }

    @Override
    protected final CGFieldList createFields(final CGClass cgClass, final CGInterfaceList superInterfaces,
            final TypeParameterMap typeParameterMap)
    {
        final CGFieldList answer = super.createFields(cgClass, superInterfaces, typeParameterMap);

        if (cgClass.isDefaultSerialVersionId())
        {
            final CGFieldFactory fieldFactory = getFieldFactory();
            final CGField cgField = fieldFactory.createDefaultSerialVersionIdField();
            answer.add(0, cgField);
        }

        return answer;
    }

    /**
     * @param superInterfaces Super interfaces.
     * @param cgClass Code generator class.
     * @param inflatedFields Inflated fields.
     * @param typeParameterMap Type parameter map.
     * 
     * @return new methods created from the given parameter.
     */
    protected final CGMethodList createMethods(final CGClass cgClass, final CGInterfaceList superInterfaces,
            final CGFieldList inflatedFields, final TypeParameterMap typeParameterMap)
    {
        final CGMethodList answer = new CGMethodList();

        final boolean isSuperPropertyChangeSupported = isSuperClassMethod(cgClass, "addPropertyChangeListener");
        final CGMethodFactory methodFactory = getMethodFactory();
        CGAnnotationList annotations = null;

        if (isSuperPropertyChangeSupported)
        {
            annotations = new CGAnnotationList();
            annotations.add(getAnnotationFactory().createOverrideAnnotation());
        }

        if (cgClass.isEqualsAndHashCode())
        {
            answer.add(methodFactory.createEqualsMethod(cgClass.getName(), inflatedFields));
            answer.add(methodFactory.createHashCodeMethod(inflatedFields));
        }

        if (cgClass.isToString())
        {
            answer.add(methodFactory.createToStringMethod());
        }

        if (isCloneable(cgClass))
        {
            answer.add(methodFactory.createCloneMethod());
        }

        if (CollectionUtils.isNotEmpty(superInterfaces))
        {
            answer.addAll(createMethods(superInterfaces, cgClass.isPropertyChangeSupported(), typeParameterMap));
        }

        super.createMethods(answer, cgClass, inflatedFields);

        return answer;
    }

    @Override
    protected final TypeParameterMap createTypeParameterMap(final CGClass cgClass, final CGInterfaceList superInterfaces)
    {
        final TypeParameterMap typeParameterMap = new TypeParameterMap(getCgTypeUtils());

        for (int i = 0; i < cgClass.getImplementsInterfaces().size(); i++)
        {
            final CGType implementsType = cgClass.getImplementsInterfaces().get(i);
            final String implementsInterface = getCgTypeUtils().toSimpleComponentNameString(implementsType);
            final String typeParameter = StringUtils.substringBetween(getCgTypeUtils().toNameString(implementsType),
                    "<", ">");

            if (StringUtils.isNotEmpty(typeParameter))
            {
                final CGType cgType = getCgTypeFactory().createType(typeParameter);
                final CGInterface superInterface = superInterfaces.findByName(implementsInterface);

                if ((superInterface != null) && (superInterface.getTypeParameters() != null)
                        && !superInterface.getTypeParameters().isEmpty())
                {
                    typeParameterMap.put(superInterface.getTypeParameters().get(0), cgType);
                }
            }
        }

        return typeParameterMap;
    }

    /**
     * 
     * @param cgInterface Code generator interface.
     * @param isPropertyChangeSupported Flag indicating if property changes are supported.
     * @param typeParameterMap Type parameter map.
     * 
     * @return new methods.
     */
    private CGMethodList createMethods(final CGInterface cgInterface, final boolean isPropertyChangeSupported,
            final TypeParameterMap typeParameterMap)
    {
        final CGMethodList answer = new CGMethodList();

        final CGMethodFactory methodFactory = getMethodFactory();
        final CGPropertyList properties = new CGPropertyList(cgInterface.getProperties());

        final CGAnnotationList annotations = new CGAnnotationList();
        annotations.add(getAnnotationFactory().createOverrideAnnotation());

        final ModifierList accessorModifiers = new ModifierList(Modifier.PUBLIC);
        answer.addAll(methodFactory.createPropertyAccessorMethods(annotations, accessorModifiers, properties,
                typeParameterMap));

        final ModifierList mutatorModifiers = new ModifierList(Modifier.PUBLIC);

        if (isPropertyChangeSupported)
        {
            answer.addAll(methodFactory.createPropertyMutatorPropertyChangeMethods(annotations, mutatorModifiers,
                    properties));
        }
        else
        {
            answer.addAll(methodFactory.createPropertyMutatorMethods(annotations, mutatorModifiers, properties,
                    typeParameterMap));
        }

        return answer;
    }

    /**
     * @param superInterfaces Super interfaces.
     * @param isPropertyChangeSupported Flag indicating if property changes are supported.
     * @param typeParameterMap Type parameter map.
     * 
     * @return new methods.
     */
    private CGMethodList createMethods(final CGInterfaceList superInterfaces, final boolean isPropertyChangeSupported,
            final TypeParameterMap typeParameterMap)
    {
        final CGMethodList answer = new CGMethodList();

        for (final CGInterface cgInterface : superInterfaces)
        {
            answer.addAll(createMethods(cgInterface, isPropertyChangeSupported, typeParameterMap));
        }

        return answer;
    }

    /**
     * @param className Class name.
     * 
     * @return a new no-argument constructor.
     */
    private CGConstructor createNoArgConstructor(final String className)
    {
        final CGConstructorFactory constructorFactory = getConstructorFactory();

        final CGTypeList referencedTypes = null;
        final CommentList commentLines = new CommentList(Collections.singletonList("Construct this object."));
        final CGAnnotationList annotations = null;
        final ModifierList modifiers = new ModifierList(Modifier.PUBLIC);
        final CGTypeList throwsTypes = null;
        final MethodBodyList methodBodyLines = new MethodBodyList(Collections.singletonList("// Nothing to do."));

        return constructorFactory.createNoArgConstructor(referencedTypes, commentLines, annotations, modifiers,
                className, throwsTypes, methodBodyLines);
    }

    /**
     * @return the annotationFactory
     */
    private CGAnnotationFactory getAnnotationFactory()
    {
        return _annotationFactory;
    }

    /**
     * @return the classFactory
     */
    private CGClassFactory getClassFactory()
    {
        return _classFactory;
    }

    /**
     * @param cgClass Code generator class.
     * 
     * @return true if the given parameter implements the <code>
     *          Cloneable</code> interface.
     */
    private boolean isCloneable(final CGClass cgClass)
    {
        boolean answer = false;

        if (cgClass != null)
        {
            final int size = cgClass.getImplementsInterfaces().size();

            for (int i = 0; !answer && (i < size); i++)
            {
                final CGType implementsInterfaces = cgClass.getImplementsInterfaces().get(i);

                if (getCgTypeUtils().equals(implementsInterfaces, _cloneableType))
                {
                    answer = true;
                }
            }
        }

        return answer;
    }

    /**
     * @param cgClass Code generator class.
     * @param methodName Method name.
     * 
     * @return true if the given parameter's superclasses specify a method of the given name.
     */
    private boolean isSuperClassMethod(final CGClass cgClass, final String methodName)
    {
        boolean answer = false;

        final List<CGType> extendsTypes = new ArrayList<CGType>();
        extendsTypes.addAll(cgClass.getImplementsInterfaces());
        final CGType extendsType = cgClass.getExtendsType();
        
        if (extendsType != null)
        {
            extendsTypes.add(extendsType);
        }

        if (!CollectionUtils.isEmpty(extendsTypes))
        {
            final int size = extendsTypes.size();

            for (int i = 0; !answer && (i < size); i++)
            {
                final CGType type = extendsTypes.get(i);
                answer = isSuperClassMethod(type, methodName);
            }
        }

        return answer;
    }

}
